<?php

/**
 * Celebrio Core
 *
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    Celebrio
 * @subpackage Core
 */

namespace Celebrio\Core;

use Nette\Object;
use \Microsoft_WindowsAzure_Storage_Table;
use \Microsoft_WindowsAzure_Storage_DynamicTableEntity;
use \Microsoft_Http_Client_Adapter_Exception;
use \Microsoft_WindowsAzure_Exception;
use Nette\Caching\Cache;
use Celebrio\Core\IConfigManagement;
use Celebrio\FileSystem\IOException;
use \Celebrio\Core\ConfigManagement;
use \Celebrio\Util\AzureUtil;
use Celebrio\Caching\CacheName;
use Celebrio\WindowsAzure\AzureStorage\AzureStorageTable;
use \InvalidArgumentException;

/**
 * Description of ConfigManagementAzure
 *
 * @author honza, pavel
 */
class ConfigManagementAzure extends Object implements IConfigManagement {
    const MAX_PROPERTY_SIZE = 65536;

    /**
     * @var \Nette\Caching\Cache
     */
    private $cache;

    /**
     * 
     * @return	\Nette\Caching\Cache
     */
    private function getCache() {
        return $this->cache;
    }

    private $celebrioAzureConfigurationTableName = "celebrioconfigs";

    public function __construct() {
        $this->cache = CacheName::generate($this);
        $this->set_cache();
    }

    public function install($moduleName, $version) {
        $this->install_module($moduleName, $version);
    }

    private function install_module($moduleName, $version) {

        if (($this->isInstalled($moduleName))) {
            throw new ConfigException("Trying to install already installed module");
        }
        $configExists = $this->entityExists($moduleName);
        if (empty($configExists)) {
            $this->save(array(), $moduleName);
        }

        $entity = $this->entityExists('installed');
        if (empty($entity)) {
            $config = array($moduleName => ConfigManagement::ON . ConfigManagement::VER_DELIMITER . $version);
            $this->save($config, 'installed');
        } else {
            $values = $entity->getAzureProperty('common');
            $values .= ";" . $moduleName . "§" . ConfigManagement::ON . ConfigManagement::VER_DELIMITER . $version;
            $entity->setAzureProperty('common', $values);
            AzureStorageTable::connection()->mergeEntity($this->celebrioAzureConfigurationTableName, $entity);
        }
        $this->update_cache();
    }

    public function uninstall($moduleName) {
        if (!self::isInstalled($moduleName)) {
            throw new ConfigException("Trying to uninstall not installed module");
        }
        $conf = $this->load_from_cache("installed");
        $conf[$moduleName] = ConfigManagement::OFF;
        $this->save($conf, "installed");
    }
    
    public function upgrade($moduleName, $version) {
        if (!self::isInstalled($moduleName)) {
            throw new ConfigException("Trying to upgrade not installed module");
            
        $conf = $this->load_from_cache("installed");
        $conf[$moduleName] = ConfigManagement::ON . ConfigManagement::VER_DELIMITER . $version;
        $this->save($conf, "installed");
        }
    }

    //TODO: refactoring of loading items from entity and using here in isInstalled
    public function isInstalled($moduleName) {
        $conf = $this->load_from_cache("installed");
        return (isset($conf[$moduleName]) && ConfigManagement::getOnOrOff($conf[$moduleName]) == ConfigManagement::ON);
    }

    public function loadConfigArray($name) {
        return $this->load_from_cache($name);
    }

    public function saveConfigArray($array, $name) {
        $this->save($array, $name);
    }

    public function deleteConfigArray($name) {
        $this->delete($name);
    }

    private function delete($name) {
        $entity = $this->entityExists($name);
        if (!empty($entity)) {
            AzureStorageTable::connection()->deleteEntity($this->celebrioAzureConfigurationTableName, $entity);
            $this->update_cache();
        } else {
            throw new \InvalidArgumentException("This entity does not exists");
        }
    }

    private function save($config, $name) {
        AzureStorageTable::connection()->createTableIfNotExists($this->celebrioAzureConfigurationTableName);

        $entity = $this->getDynamicEntity("config", $name);

        $common = '';

        foreach ($config as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $subkey => $subvalue) {
                    $common .= $key . '.' . $subkey . '§' . $subvalue . ';';
                }
            } else {
                $common .= $key . '§' . $value . ';';
            }
        }

        $common = $this->validateConfigString($common);

        if (strlen($common) < self::MAX_PROPERTY_SIZE) {
            $entity->setAzureProperty('common', $common);
        } else {
            throw new InvalidArgumentException("Config is too large.", 0);
        }

        $exists = $this->entityExists($name);
        if (!empty($exists)) {
            AzureStorageTable::connection()->updateEntity($this->celebrioAzureConfigurationTableName, $entity);
        } else {
        	AzureStorageTable::connection()->insertEntity($this->celebrioAzureConfigurationTableName, $entity);
        }
        
        $this->update_cache();
    }

    public function test() {
        return AzureStorageTable::connection()->retrieveEntities($this->celebrioAzureConfigurationTableName);
        //$entity = $table->retrieveEntityById($this->defaultCelebrioAzureConfigurationTableName, 'config', 'installed');
        //$entity = $table->retrieveEntities($this->defaultCelebrioAzureConfigurationTableName);
        //$entity = $table->retrieveEntities("testTable");
        //$this->entityExists("config-azure-test");
        //\Nette\Debug::dump($entity);
        //$this->entityExists("config-test-azure");
    }

    private function getDynamicEntity($name, $key) {
        $entity = new Microsoft_WindowsAzure_Storage_DynamicTableEntity($name, $key);
        return $entity;
    }

    private function entityExists($key) {
        //This is unfinished try to make it better
        try {
            $entity = AzureStorageTable::connection()->retrieveEntityById($this->celebrioAzureConfigurationTableName, "config", $key);
        } catch (Microsoft_WindowsAzure_Exception $e) { //If table does not exists retrieveEntityById throws an exception
            return array();
        }
        return $entity;
    }

    private function validateConfigString($config) {
        if (substr($config, -1) === ";") {
            $config = substr($config, 0, strlen($config) - 1);
        }
        if (substr($config, 0, 1) === ";") {
            $config = substr($config, 1, strlen($config));
        }

        return $config;
    }

    private function parseEntityValue($values) {
        if (empty($values))
            return array();

        $values = \explode(";", $values);

        $result = array();

        foreach ($values as $value) {
            $temp = explode("§", $value);
            if (strpos($temp[0], ".")) {
                $name = explode(".", $temp[0]);
                if (isset($result[$name[0]]) && is_array($result[$name[0]])) {
                    $result[$name[0]][$name[1]] = $temp[1];
                } else {
                    $result[$name[0]] = array();
                    $result[$name[0]][$name[1]] = $temp[1];
                }
            } else {
                $result[$temp[0]] = $temp[1];
            }
        }

        return $result;
    }
    
    /**
     * invalidates the cache for "azure_configs" key and then reads them again from database to save them once more in the cache
     */
    private function update_cache() {
    	$this->getCache()->offsetUnset("azure_configs");
    	$this->set_cache();
    }

    private function set_cache() {
    	if(!$this->getCache()->offsetExists("azure_configs")){
	        $exists = AzureStorageTable::connection()->tableExists(
	                $this->celebrioAzureConfigurationTableName
	        );
	        if ($exists) {
	            $entities = AzureStorageTable::connection()->retrieveEntities(
	                    $this->celebrioAzureConfigurationTableName
	            );
	            $boobz = array();
	            foreach ($entities as $titie) {
	                $name = $titie->getAzureProperty("rowKey");
	                $titie = $this->parseEntityValue($titie->getAzureProperty("common"));
	                $boobz[$name] = $titie;
	            }
	            $this->getCache()->save("azure_configs", $boobz);
	        }
    	}
    }

    /**
     * Load config from cache.
     * 
     * @param string $name name of config
     * @return mixed config or NULL
     */
    private function load_from_cache($name) {
        if ($this->getCache() != null) {
            $cache = $this->getCache()->offsetGet("azure_configs");
            if (isset($cache[$name])) {
                return $cache[$name];
            } else {
            	return NULL;
            }
        } else {
            throw new \InvalidStateException("Cache is not set in configuration management.");
        }
    }
}
